#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <assert.h>
#include <xp/config.h>

#ifndef BYTE_BITS
#define BYTE_BITS 8
#endif

struct type_t
{
	const char* name;
	const char* dname;
	int         size;
	int         valid;
};

typedef struct type_t type_t;

type_t char_type[] =
{
	{"char",               "mchar", sizeof(char), 1},
	{"int",                "mcint", sizeof(int),  1},
	{NULL, 0, 0, 0}
};

type_t int_type[] = 
{
	{"char",               "int", sizeof(char),                1},
	{"short",              "int", sizeof(short),               1},
	{"int",                "int", sizeof(int),                 1},
	{"long",               "int", sizeof(long),                1},
#if defined(__STDC_VERSION__)&&(__STDC_VERSION__>=199901L)
	{"long long",          "int", sizeof(long long),           1},
#elif defined(XP_HAVE_LONG_LONG)
	{"long long",          "int", sizeof(long long),           1},
#endif
#if defined(_WIN32)
	{"__int8",             "int", sizeof(__int8),              1},
	{"__int16",            "int", sizeof(__int16),             1},
	{"__int32",            "int", sizeof(__int32),             1},
	{"__int64",            "int", sizeof(__int64),             1},
#endif
#if defined(__vms)&&defined(__alpha)
	{"__int64",            "int", sizeof(__int64),             1},
#endif

	{"unsigned char",      "uint", sizeof(unsigned char),      1},
	{"unsigned short",     "uint", sizeof(unsigned short),     1},
	{"unsigned int",       "uint", sizeof(unsigned int),       1},
	{"unsigned long",      "uint", sizeof(unsigned long),      1},
#if defined(__STDC_VERSION__)&&(__STDC_VERSION__>=199901L)
	{"unsigned long long", "uint", sizeof(unsigned long long), 1},
#elif defined(XP_HAVE_LONG_LONG)
	{"unsigned long long", "uint", sizeof(unsigned long long), 1},
#endif
#if defined(_WIN32)
	{"unsigned __int8",    "uint", sizeof(unsigned __int8),    1},
	{"unsigned __int16",   "uint", sizeof(unsigned __int16),   1},
	{"unsigned __int32",   "uint", sizeof(unsigned __int32),   1},
	{"unsigned __int64",   "uint", sizeof(unsigned __int64),   1},
#endif
#if defined(__vms)&&defined(__alpha)
	{"unsigned __int64",   "uint", sizeof(unsigned __int64),   1},
#endif

	{NULL, 0, 0, 0}
};

type_t float_type[] =
{

	{"float",       "float",       sizeof(float),       1},
	{"double",      "double",      sizeof(double),      1},
#if defined(XP_HAVE_LONG_DOUBLE)
	{"long double", "long double", sizeof(long double), 1},
#endif
	{NULL, 0, 0, 0} 
};

static int get_type_width (void)
{
	type_t* p;
	int width = 0;

	for (p = char_type; p->name != NULL; p++) {
		if (strlen(p->name) > width) width = strlen(p->name);	
	}

	for (p = int_type; p->name != NULL; p++) {
		if (strlen(p->name) > width) width = strlen(p->name);	
	}

	for (p = float_type; p->name != NULL; p++) {
		if (strlen(p->name) > width) width = strlen(p->name);	
	}

	return width;
}

/* integers of various sizes */
static void define_int_t (const char* prefix, int width)
{
	type_t* p;

	for (p = int_type; p->name != NULL; p++) {
		if (p->valid) {
			type_t* q = int_type;
			while (q->name != NULL) {
				if (p != q && q->valid && 
				    strcmp (p->dname, q->dname) == 0 && 
				    p->size == q->size) {
					q->valid = 0;
				}
				q++;
			}
		}
	}

	for (p = int_type; p->name != NULL; p++) {
		if (p->valid) {
			printf ("typedef %-*s %s%s%d_t;\n", 
				width, p->name, prefix, p->dname, p->size * BYTE_BITS);
		}
	}
	
}

/* the integer type whose size is equal to that of a pointer */
static void define_size_t (const char* prefix, int width)
{
	int size;
	int size1 = sizeof(char*);
	int size2 = sizeof(void(*)(void));
	type_t* p;

	size = (size1 > size2)? size1: size2;

	for (p = int_type; p->name != NULL; p++) {
		if (p->valid && p->size == size && 
		    strncmp(p->name, "unsigned", 8) == 0) {
			printf ("typedef %-*s %ssize_t;\n", width, p->name, prefix);
			break;
		}
	}

	for (p = int_type; p->name != NULL; p++) {
		if (p->valid && p->size == size && 
		    strncmp(p->name, "unsigned", 8) != 0) {
			printf ("typedef %-*s %sssize_t;\n", width, p->name, prefix);
			break;
		}
	}
}

/* the integer type whose size is equal to that of a pointer */
static void define_general_int_t (const char* prefix, int width)
{
	int size;
	int size1 = sizeof(char*);
	int size2 = sizeof(void(*)(void));
	type_t* p;

	size = (size1 > size2)? size1: size2;

	printf ("#define XP_POINTER_SIZE %d\n", size);

	for (p = int_type; p->name != NULL; p++) {
		if (p->valid && p->size == size && 
		    strncmp(p->name, "unsigned", 8) == 0) {
			printf ("#define XP_INT_T_SIZE %d\n", p->size);
			printf ("typedef %-*s %sint_t;\n", width, p->name, prefix);
			break;
		}
	}

	for (p = int_type; p->name != NULL; p++) {
		if (p->valid && p->size == size && 
		    strncmp(p->name, "unsigned", 8) != 0) {
			printf ("#define XP_UINT_T_SIZE %d\n", p->size);
			printf ("typedef %-*s %suint_t;\n", width, p->name, prefix);
			break;
		}
	}
}

/* the largest integer type available */
static void define_general_long_t (const char* prefix, int width)
{
	type_t* p, * q;


	q = NULL;
	for (p = int_type; p->name != NULL; p++) {
		if (strncmp(p->name, "unsigned", 8) != 0) {
			if (q == NULL) q = p;
			else if (p->size > q->size) q = p;
		}
	}

	if (q != NULL) {
		printf ("#define XP_LONG_T_SIZE %d\n", q->size);
		printf ("typedef %-*s %slong_t;\n", width, q->name, prefix);
	}

	q = NULL;
	for (p = int_type; p->name != NULL; p++) {
		if (strncmp(p->name, "unsigned", 8) == 0) {
			if (q == NULL) q = p;
			else if (p->size > q->size) q = p;
		}
	}

	if (q != NULL) {
		printf ("#define XP_ULONG_T_SIZE %d\n", q->size);
		printf ("typedef %-*s %sulong_t;\n", width, q->name, prefix);
	}
}

static void define_char_t (const char* prefix, int width)
{
	type_t* p;
	int wint_t_size;

	/*
	printf ("typedef rb_int8 %smchar_t;\n", prefix);
	printf ("typedef rb_int32 %smcint_t;\n", prefix);
	*/
	for (p = char_type; p->name != NULL; p++) {
		if (p->valid) {
			printf ("typedef %-*s %s%s_t;\n", width, p->name, prefix, p->dname);
		}
	}

	for (p = int_type; p->name != NULL; p++) {
		if ((sizeof(wchar_t) <= 2 && strncmp(p->name, "unsigned", 8) == 0) ||
		    (sizeof(wchar_t) > 2 && strncmp(p->name, "unsigned", 8) != 0)) {
			/*if (p->size == sizeof(L'a')) { */
			if (p->size == sizeof(wchar_t)) {
				printf ("#define XP_HAVE_WCHAR_T\n");
				printf ("typedef %-*s %swchar_t;\n", width, p->name, prefix);
				break;
			}
		}
	}

	wint_t_size = (sizeof(wchar_t) < sizeof(int))? sizeof(int): sizeof(wchar_t);
	for (p = int_type; p->name != NULL; p++) {
		if ((sizeof(wchar_t) <= 2 && strncmp(p->name, "unsigned", 8) == 0) ||
		    (sizeof(wchar_t) > 2 && strncmp(p->name, "unsigned", 8) != 0)) {
			if (p->size == wint_t_size) {
				printf ("#define XP_HAVE_WCINT_T\n");
				printf ("typedef %-*s %swcint_t;\n", width, p->name, prefix);
				break;
			}
		}
	}
}

static void define_real_t (const char* prefix, int width)
{
	type_t* p, * q;

	q = NULL;
	for (p = float_type; p->name != NULL; p++) {
		if (q == NULL) q = p;
		else if (p->size > q->size) q = p;
	}

	if (q != NULL) {
		printf ("typedef %-*s %sreal_t;\n", width, q->name, prefix);
	}
}

int main (int argc, char* argv[])
{
	int width;
	const char* prefix = "";
	
	if (argc > 2) {
		fprintf (stderr, "usage: %s [prefix]\n", argv[0]);
		return -1;
	}
	else if (argc == 2) prefix = argv[1];

	width = get_type_width ();

	printf ("\n");
	define_int_t (prefix, width);

	printf ("\n");
	define_size_t (prefix, width);

	printf ("\n");
	define_general_int_t (prefix, width);

	printf ("\n");
	define_general_long_t (prefix, width);

	printf ("\n");
	define_char_t (prefix, width);

	printf ("\n");
	define_real_t (prefix, width);

	printf ("\n");
	return 0;
}
